package lib

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"go.etcd.io/etcd/api/v3/mvccpb"
	clientv3 "go.etcd.io/etcd/client/v3"
)

type EtcdResolver struct {
	client *clientv3.Client

	mutex   sync.Mutex
	cancels []func()
}

var (
	resolver *EtcdResolver
	once     sync.Once
)

// 负载均衡模块中获取 etcd 客户端单例，连接参数通过配置文件获取
func GetEtcdResolverOr() (*EtcdResolver, error) {
	var err error
	once.Do(func() {
		var cli *clientv3.Client
		// fmt.Println(ConfEtcd.Hosts, ConfEtcd.DialTimeout)
		cli, err = NewEtcdClient(ConfEtcd.Hosts, time.Duration(ConfEtcd.DialTimeout))

		if err != nil {
			return
		}

		resolver = &EtcdResolver{
			client: cli,
		}
	})
	if err != nil {
		return nil, err
	}
	if resolver == nil {
		return nil, errors.New("get etcd manager failed")
	}
	return resolver, nil
}

func (e *EtcdResolver) GetServerListByPrefix(serverName string) ([]string, error) {
	prefix := instanceKey(serverName)
	resp, err := e.client.Get(context.Background(), prefix, clientv3.WithPrefix())
	if err != nil {
		return nil, err
	}
	list := []string{}
	for _, kv := range resp.Kvs {
		list = append(list, string(kv.Value))
	}
	return list, nil
}

func (e *EtcdResolver) WatchServerListByPrefix(serverName string) (chan []string, chan error) {
	snapshots := make(chan []string)
	errCh := make(chan error)

	ctx, cancel := context.WithCancel(context.Background())
	e.mutex.Lock()
	e.cancels = append(e.cancels, cancel)
	e.mutex.Unlock()

	prefix := instanceKey(serverName)
	wCh := e.client.Watch(ctx, prefix, clientv3.WithPrefix())
	go func() {
		for {
			select {
			case wResp := <-wCh:
				for _, event := range wResp.Events {
					switch event.Type {
					case mvccpb.PUT, mvccpb.DELETE:
						snapshot, err := e.GetServerListByPrefix(serverName)
						if err != nil {
							errCh <- err
						}
						snapshots <- snapshot
					}
				}
			case <-ctx.Done():
				return
			}
		}
	}()

	return snapshots, errCh
}

func (e *EtcdResolver) Close() {
	e.mutex.Lock()
	cancels := e.cancels
	e.cancels = nil
	e.mutex.Unlock()
	for _, cancel := range cancels {
		cancel()
	}

	e.client.Close()
}

func NewEtcdClient(hosts []string, timeout time.Duration) (*clientv3.Client, error) {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   hosts,
		DialTimeout: timeout * time.Second,
	})
	if err != nil {
		fmt.Printf("NewEtcdClient: %v", err)
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	_, err = cli.Get(ctx, "hello") // 尝试获取一个键值
	if err != nil {
		// 处理连接错误
		fmt.Printf("NewEtcdClient 无法连接到etcd服务: %v\n", err)
		return nil, err
	}
	return cli, nil
}

func instanceKey(prefix string) string {
	return fmt.Sprintf("/gateway/%s", prefix)
}
